package com.springboot.file.service.impl;

import com.springboot.file.exception.BusinessException;
import com.springboot.file.param.req.ExportWordReq;
import com.springboot.file.param.resp.ExportExcelResp;
import com.springboot.file.provider.DocumentProvider;
import com.springboot.file.provider.DocumentProviderFactory;
import com.springboot.file.service.CommonService;
import com.springboot.file.util.DocumentUtil;
import com.springboot.file.util.easyexcel.ExceptionUtils;
import com.springboot.file.util.easyexcel.EasyExcelUtil;
import com.springboot.file.util.easyexcel.provider.ExportProvider;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.Timer;
import java.util.TimerTask;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import org.springframework.util.StringUtils;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.UUID;

@Slf4j
@Service
public class CommonServiceImpl implements CommonService {
    @Resource
    private EasyExcelUtil easyExcelUtil;

    @Value("${document.template.path:classpath:templates/}")
    private String templatePath;
    
    @Value("${file.temp.path:temp}")
    private String tempPath;

    @Resource
    private DocumentProviderFactory documentProviderFactory;

    /**
     * 临时文件存储目录
     */
    private static final String TEMP_DIR = System.getProperty("java.io.tmpdir");
    
    /**
     * Excel文件扩展名
     */
    private static final String EXCEL_EXTENSION = ".xlsx";
    
    /**
     * 默认块大小，2MB
     */
    private static final int DEFAULT_BLOCK_SIZE = 2 * 1024 * 1024;
    
    /**
     * 默认清理延迟时间（分钟）
     */
    private static final int DEFAULT_CLEANUP_DELAY_MINUTES = 30;
    
    /**
     * 毫秒到分钟的转换因子
     */
    private static final long MILLISECONDS_PER_MINUTE = 60L * 1000L;
    
    /**
     * 数据量阈值，超过此阈值将分拆为多个文件导出
     * 设置为10万条记录，超过此值将拆分为多个文件
     */
    private static final long MULTI_FILE_THRESHOLD = 100000;
    
    /**
     * 单个Excel文件的最大记录数
     * 设置为10万条记录，每个文件最多包含此数量的记录
     */
    private static final long MAX_RECORDS_PER_FILE = 100000;
    
    /**
     * 导出ID参数名
     */
    private static final String PARAM_EXPORT_ID = "exportId";
    
    /**
     * 起始记录参数名
     */
    private static final String PARAM_START_RECORD = "startRecord";
    
    /**
     * 结束记录参数名
     */
    private static final String PARAM_END_RECORD = "endRecord";
    
    /**
     * 文件索引参数名
     */
    private static final String PARAM_FILE_INDEX = "fileIndex";
    
    /**
     * 块索引参数名
     */
    private static final String PARAM_CHUNK_INDEX = "chunkIndex";
    
    /**
     * 默认块索引值（始终为0）
     */
    private static final Integer DEFAULT_CHUNK_INDEX = 0;

    /**
     * 通用的业务导入功能
     * @param businessType 业务类型
     * @param file 导入文件
     */
    public void importExcel(String businessType, MultipartFile file) {
        try {
            easyExcelUtil.importData(businessType, file);
        } catch (Exception e) {
            // 使用ExceptionUtils处理异常
            ExceptionUtils.ExceptionHandlingResult result = 
                ExceptionUtils.handleExcelProcessingException(e, "单表导入");
            
            if (result.shouldThrow()) {
                throw result.getAsRuntimeException();
            }
        }
    }

    /**
     * 多表格导入功能 - 将Excel中不同的sheet导入到不同的表中
     * 高性能线程池优化版本 - 使用并行处理提高导入速度
     * 支持事务管理，可以在所有sheet内容保存时使用同一个事务
     *
     * @param businessTypes 业务类型字符串，支持以下格式:
     *                     1. 带引号的逗号分隔格式: "CUST_CREDIT","INVOICE"
     *                     2. 不带引号的逗号分隔格式: CUST_CREDIT,INVOICE
     *                     3. JSON数组格式: ["CUST_CREDIT","INVOICE"]
     *                     4. JSON对象格式: {"CUST_CREDIT","INVOICE"}
     * @param file 导入文件
     * @param enableTransaction 是否启用事务管理
     */
    @Override
    public void importMultiSheetExcel(String businessTypes, MultipartFile file, Boolean enableTransaction) {
        List<String> typeList = parseBusinessTypes(businessTypes);
        log.info("导入多Sheet数据，类型列表: {}, 事务管理: {}", typeList, enableTransaction);
        
        if (Boolean.TRUE.equals(enableTransaction)) {
            importWithTransaction(typeList, file);
        } else {
            importWithoutTransaction(typeList, file);
        }
    }
    
    /**
     * 使用事务导入多Sheet数据
     * 所有sheet在一个事务内，任何sheet失败都会导致完整回滚
     * 
     * @param typeList 业务类型列表
     * @param file 导入文件
     */
    private void importWithTransaction(List<String> typeList, MultipartFile file) {
        try {
            // 使用事务处理所有sheet
            easyExcelUtil.importMultiSheetExcel(typeList, file, true);
        } catch (Exception e) {
            // 使用ExceptionUtils处理异常
            ExceptionUtils.ExceptionHandlingResult result = 
                ExceptionUtils.handleExcelProcessingException(e, "多表格事务导入");
            
            if (result.shouldThrow()) {
                throw result.getAsRuntimeException();
            }
        }
    }
    
    /**
     * 非事务模式导入多Sheet数据
     * 每个sheet独立处理，某个sheet失败不影响其他sheet
     * 
     * @param typeList 业务类型列表
     * @param file 导入文件
     */
    private void importWithoutTransaction(List<String> typeList, MultipartFile file) {
        try {
            // 使用非事务模式，每个sheet独立处理
            easyExcelUtil.importMultiSheetExcel(typeList, file, false);
        } catch (Exception e) {
            // 使用ExceptionUtils处理异常
            ExceptionUtils.ExceptionHandlingResult result = 
                ExceptionUtils.handleExcelProcessingException(e, "多表格非事务导入");
            
            // 对于非事务模式，需要特殊处理部分成功部分失败的情况
            if (result.getExceptionType() == ExceptionUtils.ExceptionType.BUSINESS) {
                String errorMessage = result.getErrorMessage();
                
                // 检查是否包含特殊的部分成功部分失败消息格式
                if (isPartialSuccessMessage(errorMessage)) {
                    log.warn("部分sheet导入成功，部分失败: {}", errorMessage);
                    throw new BusinessException(errorMessage);
                } else if (errorMessage.contains("以下sheet导入失败") || errorMessage.contains("个失败") || errorMessage.contains("导入失败")) {
                    log.warn("部分sheet导入失败: {}", errorMessage);
                    throw new BusinessException(errorMessage);
                }
            }
            
            if (result.shouldThrow()) {
                throw result.getAsRuntimeException();
            }
        }
    }
    
    /**
     * 判断是否是部分成功部分失败的消息格式
     * 
     * @param message 错误消息
     * @return 是否是部分成功的格式
     */
    private boolean isPartialSuccessMessage(String message) {
        return message.contains("共") && 
               message.contains("个sheet") && 
               message.contains("个成功") && 
               message.contains("个失败");
    }

    @Override
    public void exportExcel(String businessType, Map<String, Object> params,
                            Map<String, String> headers,
                            HttpServletResponse response) {
        log.info("开始导出业务类型 [{}] 的数据, 参数: {}", businessType, params);
        // 调用EasyExcelUtil执行导出
        easyExcelUtil.exportData(businessType, params, headers, response);
        log.info("业务类型 [{}] 的数据导出完成", businessType);
    }

    /**
     * 从参数中提取exportId
     * 
     * @param params 请求参数
     * @return 导出ID，如果不存在则返回null
     */
    private String extractExportId(Map<String, Object> params) {
        if (params != null && params.containsKey(PARAM_EXPORT_ID)) {
            Object id = params.get(PARAM_EXPORT_ID);
            if (id != null) {
                return id.toString();
            }
        }
        return null;
    }
    
    /**
     * 判断是否是第一个块的请求
     * 
     * @param chunkIndex 块索引
     * @param exportId 导出ID
     * @return 是否是第一个块的请求
     */
    private boolean isFirstChunkRequest(Integer chunkIndex, String exportId) {
        return DEFAULT_CHUNK_INDEX.equals(chunkIndex) && (exportId == null || exportId.isEmpty());
    }

    /**
     * 分块导出Excel文件为二进制数据
     * 此方法可以处理超大Excel文件的导出，避免内存溢出问题
     * 
     * @param businessType 业务类型
     * @param params 导出条件参数，包含chunkIndex和chunkId
     * @param headers 动态表头
     * @param fileName 文件名
     * @return 包含当前块二进制数据的响应对象
     */
    private ExportExcelResp exportExcelChunked(String businessType, Map<String, Object> params,
                                           Map<String, String> headers, String fileName) {
        try {
            // 从params中获取chunkIndex
            Integer chunkIndex = extractChunkIndex(params);
            
            log.info("处理分块二进制导出业务类型 [{}] 的数据, 块索引: {}, 参数: {}", businessType, chunkIndex, params);
            
            // 提取exportId (如果存在)
            String exportId = extractExportId(params);
            
            // 处理不同类型的导出请求
            return handleExportRequest(businessType, params, headers, fileName, chunkIndex, exportId);
        } catch (Exception e) {
            log.error("分块导出Excel异常：", e);
            throw new BusinessException("分块导出Excel异常：" + e.getMessage());
        }
    }
    
    /**
     * 从参数中提取块索引
     * 
     * @param params 请求参数
     * @return 块索引，如果不存在则返回默认值
     */
    private Integer extractChunkIndex(Map<String, Object> params) {
        Integer chunkIndex = null;
        if (params != null && params.containsKey(PARAM_CHUNK_INDEX)) {
            Object chunkIndexObj = params.get(PARAM_CHUNK_INDEX);
            if (chunkIndexObj != null) {
                if (chunkIndexObj instanceof Integer integer) {
                    chunkIndex = integer;
                } else {
                    try {
                        chunkIndex = Integer.parseInt(chunkIndexObj.toString());
                    } catch (NumberFormatException e) {
                        log.warn("无法解析{}参数: {}", PARAM_CHUNK_INDEX, chunkIndexObj);
                    }
                }
            }
        }
        
        // 默认块索引为0
        return chunkIndex != null ? chunkIndex : DEFAULT_CHUNK_INDEX;
    }
    
    /**
     * 处理导出请求，根据参数类型选择不同的处理方式
     * 
     * @param businessType 业务类型
     * @param params 导出参数
     * @param headers 表头
     * @param fileName 文件名
     * @param chunkIndex 块索引
     * @param exportId 导出ID
     * @return 导出结果
     */
    private ExportExcelResp handleExportRequest(String businessType, Map<String, Object> params,
                                             Map<String, String> headers, String fileName,
                                             Integer chunkIndex, String exportId) {
        // 检查是否是多文件模式的后续请求
        if (isMultiFileRequest(chunkIndex, exportId)) {
            MultiFileExportState multiFileState = multiFileExportStates.get(exportId);
            if (multiFileState != null) {
                // 处理多文件模式的请求
                log.info("检测到多文件导出请求, ID: {}, 文件索引: {}", exportId, params.get(PARAM_FILE_INDEX));
                return handleMultiFileRequest(multiFileState, params, headers, chunkIndex);
            }
        }
        
        // 处理导出请求
        if (isFirstChunkRequest(chunkIndex, exportId)) {
            return handleFirstChunkRequest(businessType, params, headers, fileName);
        } else {
            return handleSubsequentChunkRequest(exportId, chunkIndex);
        }
    }
    
    /**
     * 判断是否是多文件模式的请求
     */
    private boolean isMultiFileRequest(Integer chunkIndex, String exportId) {
        return !isFirstChunkRequest(chunkIndex, exportId) && exportId != null;
    }
    
    /**
     * 处理第一个块的导出请求
     * 
     * @param businessType 业务类型
     * @param params 导出参数
     * @param headers 表头
     * @param fileName 文件名
     * @return 块数据响应对象
     */
    private ExportExcelResp handleFirstChunkRequest(String businessType, Map<String, Object> params,
                                                   Map<String, String> headers, String fileName) {
        // 生成新的唯一导出ID
        String exportId = UUID.randomUUID().toString();
        log.info("开始新的分块导出，导出ID: {}, 参数: {}", exportId, params);
        
        // 创建临时文件
        String tempFilePath = TEMP_DIR + File.separator + exportId + EXCEL_EXTENSION;
        File tempFile = new File(tempFilePath);
        
        try {
            // 导出到临时文件
            try (FileOutputStream fileOut = new FileOutputStream(tempFile)) {
                // 检查是否是多文件模式的特定文件导出
                boolean isMultiFilePart = params.containsKey(PARAM_FILE_INDEX) && 
                                         params.containsKey(PARAM_START_RECORD) && 
                                         params.containsKey(PARAM_END_RECORD);
                
                if (isMultiFilePart) {
                    log.info("多文件导出模式，文件索引: {}, 范围: {}-{}", 
                           params.get(PARAM_FILE_INDEX), params.get(PARAM_START_RECORD), params.get(PARAM_END_RECORD));
                }
                
                // 使用通用导出方法 - 直接传递所有参数
                // 如果是多文件模式，params中已包含startRecord和endRecord
                // 导出提供者需要在实现中支持这些参数
                easyExcelUtil.exportDataInternal(businessType, params, headers, () -> fileOut);
            }
            
            // 设置导出状态
            createAndStoreExportState(exportId, businessType, fileName, tempFilePath, tempFile.length());
            
            // 读取第一个块
            return readChunkFromFile(chunkedExportStates.get(exportId), 0);
        } catch (Exception e) {
            log.error("处理第一个块导出异常：", e);
            throw new BusinessException("处理第一个块导出异常：" + e.getMessage());
        }
    }
    
    /**
     * 创建并存储导出状态对象
     */
    private void createAndStoreExportState(String exportId, String businessType, String fileName, 
                                          String tempFilePath, long fileSize) {
        // 计算分块数量
        int totalChunks = (int) Math.ceil((double) fileSize / DEFAULT_BLOCK_SIZE);
        
        // 创建导出状态对象
        ChunkedExportState exportState = new ChunkedExportState();
        exportState.setExportId(exportId);
        exportState.setFileName(fileName);
        exportState.setFilePath(tempFilePath);
        exportState.setFileSize(fileSize);
        exportState.setBlockSize(DEFAULT_BLOCK_SIZE);
        exportState.setTotalChunks(totalChunks);
        exportState.setBusinessType(businessType);
        
        // 存储导出状态
        chunkedExportStates.put(exportId, exportState);
        
        log.info("Excel文件已导出到临时文件: {}, 总大小: {}KB, 总块数: {}", 
                tempFilePath, fileSize / 1024, totalChunks);
    }
    
    /**
     * 处理后续块的导出请求
     * 
     * @param exportId 导出ID
     * @param chunkIndex 块索引
     * @return 块数据响应对象
     */
    private ExportExcelResp handleSubsequentChunkRequest(String exportId, Integer chunkIndex) {
        if (exportId == null || exportId.isEmpty()) {
            throw new BusinessException("导出ID不能为空");
        }
        
        // 获取导出状态
        ChunkedExportState exportState = chunkedExportStates.get(exportId);
        
        if (exportState == null) {
            throw new BusinessException("找不到导出记录，可能已过期或导出ID无效: " + exportId);
        }
        
        // 读取请求的块
        return readChunkFromFile(exportState, chunkIndex);
    }

    /**
     * 从临时文件中读取指定块
     * 
     * @param exportState 导出状态
     * @param chunkIndex 块索引
     * @return 块数据响应对象
     */
    private ExportExcelResp readChunkFromFile(ChunkedExportState exportState, int chunkIndex) {
        try {
            File tempFile = new File(exportState.getFilePath());
            
            if (!tempFile.exists()) {
                throw new BusinessException("临时文件不存在：" + exportState.getFilePath());
            }
            
            // 计算块的起始位置和大小
            long startPos = (long) chunkIndex * exportState.getBlockSize();
            int currentBlockSize = (int) Math.min(exportState.getBlockSize(), 
                                                 exportState.getFileSize() - startPos);
            
            // 如果索引超出范围，返回错误
            if (startPos >= exportState.getFileSize()) {
                throw new BusinessException("块索引超出范围：" + chunkIndex);
            }
            
            // 读取指定位置的数据块
            byte[] buffer = new byte[currentBlockSize];
            
            // 通过RandomAccessFile读取数据 - 确保完全读取
            try (RandomAccessFile raf = new RandomAccessFile(tempFile, "r")) {
                raf.seek(startPos);
                int bytesRead = raf.read(buffer, 0, currentBlockSize);
                
                // 如果读取的字节数少于请求的字节数，调整buffer大小
                if (bytesRead < currentBlockSize) {
                    log.warn("读取文件块不完整，请求 {} 字节但只读取到 {} 字节", 
                            currentBlockSize, bytesRead);
                    
                    // 创建新的正确大小的缓冲区
                    byte[] actualBuffer = new byte[bytesRead];
                    System.arraycopy(buffer, 0, actualBuffer, 0, bytesRead);
                    buffer = actualBuffer;
                    currentBlockSize = bytesRead;
                }
            }
            
            // 检查是否是最后一块
            boolean isLastChunk = (chunkIndex >= exportState.getTotalChunks() - 1);
            
            // 如果是最后一块，并且导出状态设置了自动清理，则安排清理任务
            if (isLastChunk && exportState.isAutoCleanup()) {
                scheduleCleanup(exportState.getExportId());
            }
            
            // 构建响应对象
            ExportExcelResp resp = new ExportExcelResp();
            resp.setFileName(exportState.getFileName() + EXCEL_EXTENSION);
            resp.setFileType("xlsx");
            resp.setChunkedMode(true);
            resp.setChunkId(exportState.getExportId());
            resp.setChunkIndex(chunkIndex);
            resp.setTotalChunks(exportState.getTotalChunks());
            resp.setIsLastChunk(isLastChunk);
            
            // 使用二进制数据而不是Base64编码
            // 使用新方法设置文件内容
            resp.withFileContent(buffer);
            
            // 确保所有必要字段都已设置
            if (resp.getChunkId() == null || resp.getChunkId().isEmpty()) {
                log.error("严重错误: 生成的响应缺少chunkId");
            }
            
            // 检查文件内容是否有效
            if (!resp.hasValidContent()) {
                log.error("严重错误: 文件内容无效，长度为 {}", 
                        resp.getFileContent() == null ? "null" : resp.getFileContent().length);
            } else {
                log.info("文件块读取成功: 大小={}KB, 文件类型={}", 
                        resp.getFileContent().length / 1024, resp.getFileType());
            }
            
            log.info("返回块数据, 导出ID: {}, 块索引: {}/{}, 块大小: {}KB, 响应结构: {}",
                    exportState.getExportId(), chunkIndex, exportState.getTotalChunks() - 1, 
                    currentBlockSize / 1024, resp.toLogString());
            
            return resp;
        } catch (Exception e) {
            log.error("读取数据块异常：", e);
            throw new BusinessException("读取数据块异常：" + e.getMessage());
        }
    }

    /**
     * 安排清理任务，在默认延迟时间后清理临时文件
     * 
     * @param exportId 导出ID
     */
    private void scheduleCleanup(String exportId) {
        Timer timer = new Timer(true);
        // 使用常量显式转换为long，避免隐式转换
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                try {
                    cleanupExport(exportId);
                } catch (Exception e) {
                    log.error("清理导出资源异常", e);
                }
            }
        }, DEFAULT_CLEANUP_DELAY_MINUTES * MILLISECONDS_PER_MINUTE);
    }

    /**
     * 清理导出资源
     * 
     * @param exportId 导出ID
     */
    private void cleanupExport(String exportId) {
        ChunkedExportState exportState = chunkedExportStates.remove(exportId);
        
        if (exportState != null) {
            try {
                Path tempFilePath = Paths.get(exportState.getFilePath());
                if (Files.exists(tempFilePath)) {
                    Files.delete(tempFilePath);
                    log.info("临时文件已删除: {}", exportState.getFilePath());
                } else {
                    log.info("临时文件不存在: {}", exportState.getFilePath());
                }
            } catch (IOException e) {
                log.error("删除临时文件异常: {}", e.getMessage(), e);
            }
        }
    }

    /**
     * 存储分块导出状态的静态Map
     * Key: 导出ID, Value: 导出状态
     */
    private static final Map<String, ChunkedExportState> chunkedExportStates = 
            Collections.synchronizedMap(new HashMap<>());

    /**
     * 分块导出状态类
     */
    @Data
    private static class ChunkedExportState {
        // 导出ID
        private String exportId;
        // 业务类型
        private String businessType;
        // 文件名
        private String fileName;
        // 临时文件路径
        private String filePath;
        // 文件大小
        private long fileSize;
        // 块大小
        private int blockSize;
        // 总块数
        private int totalChunks;
        // 是否自动清理
        private boolean autoCleanup = true;
    }

    /**
     * 清理导出的临时文件
     * @param exportId 导出ID
     * @return 是否清理成功
     */
    @Override
    public boolean cleanupExportFile(String exportId) {
        log.info("开始清理导出临时文件, 导出ID: {}", exportId);
        
        if (exportId == null || exportId.isEmpty()) {
            log.warn("清理临时文件失败: 导出ID为空");
            return false;
        }
        
        ChunkedExportState exportState = chunkedExportStates.remove(exportId);
        
        if (exportState != null) {
            try {
                Path tempFilePath = Paths.get(exportState.getFilePath());
                if (Files.exists(tempFilePath)) {
                    Files.delete(tempFilePath);
                    log.info("临时文件已成功删除: {}", exportState.getFilePath());
                    return true;
                } else {
                    log.info("临时文件不存在，可能已被删除: {}", exportState.getFilePath());
                    return true;
                }
            } catch (IOException e) {
                log.error("删除临时文件异常: {}", e.getMessage(), e);
                
                // 如果删除失败，尝试使用JVM退出时删除
                try {
                    File tempFile = new File(exportState.getFilePath());
                    tempFile.deleteOnExit();
                    log.info("临时文件已标记为JVM退出时删除");
                } catch (Exception ex) {
                    log.error("标记临时文件JVM退出时删除失败", ex);
                }
                
                return false;
            }
        } else {
            log.warn("找不到导出ID对应的临时文件信息: {}", exportId);
            return false;
        }
    }

    /**
     * 智能导出Excel，根据数据量自动选择单文件导出或多文件分块导出
     * 当数据量小时直接导出单个文件，数据量大时分成多个文件导出
     * 
     * @param businessType 业务类型
     * @param params 导出条件参数
     * @param headers 动态表头
     * @param fileName 文件名
     * @param response HTTP响应对象，用于单文件导出场景
     * @return 导出结果，对于分块导出会返回第一个块的信息，对于单文件导出返回null
     */
    @Override
    public ExportExcelResp smartExportExcel(String businessType, Map<String, Object> params, 
                                           Map<String, String> headers, String fileName, 
                                           HttpServletResponse response) {
        try {
            log.info("开始智能导出业务类型 [{}] 的数据, 参数: {}", businessType, params);
            
            // 获取导出总数据量
            ExportProvider<?> exportProvider = easyExcelUtil.getExportProvider(businessType);
            if (exportProvider == null) {
                throw new BusinessException("未找到业务类型 [" + businessType + "] 对应的导出提供者");
            }
            
            // 获取导出数据总量
            long totalCount = exportProvider.getExportDataCount(params);
            log.info("导出数据总量: {}, 阈值: {}", totalCount, MULTI_FILE_THRESHOLD);
            
            // 数据量为0，使用普通导出返回空文件
            if (totalCount <= 0) {
                log.info("数据量为0，导出空文件");
                easyExcelUtil.exportData(businessType, params, headers, response);
                return null;
            }
            
            // 判断数据量是否超过阈值
            if (totalCount <= MULTI_FILE_THRESHOLD) {
                // 数据量较小，直接导出单文件
                log.info("数据量 {} 未超过阈值 {}，使用普通导出单文件", totalCount, MULTI_FILE_THRESHOLD);
                
                // 检查是否请求参数指定了要使用分块模式
                boolean forceChunkedMode = params != null && params.containsKey("forceChunkedMode") && 
                                         Boolean.TRUE.equals(params.get("forceChunkedMode"));
                                         
                if (forceChunkedMode) {
                    log.info("请求指定使用分块模式，即使数据量较小也使用分块导出");
                    return handleSmallDatasetInChunkedMode(businessType, params, headers, fileName);
                } else {
                    // 使用普通导出，直接写入response
                    easyExcelUtil.exportData(businessType, params, headers, response);
                    return null;
                }
            } else {
                // 数据量较大，使用多文件拆分导出
                int totalFiles = (int) Math.ceil((double) totalCount / MAX_RECORDS_PER_FILE);
                log.info("数据量 {} 超过阈值 {}，将拆分为 {} 个文件导出，每个文件最多 {} 条记录", 
                        totalCount, MULTI_FILE_THRESHOLD, totalFiles, MAX_RECORDS_PER_FILE);
                
                // 检查是否需要自动打包导出
                boolean enablePackage = params != null && params.containsKey("enablePackage") && 
                                       Boolean.TRUE.equals(params.get("enablePackage"));
                
                if (enablePackage && totalFiles > 1) {
                    log.info("启用自动打包导出模式，将 {} 个文件打包为ZIP文件下载", totalFiles);
                    return handleMultiFilePackagedExport(businessType, params, headers, fileName, totalCount);
                } else {
                    // 处理多文件拆分导出
                    return handleMultiFileExport(businessType, params, headers, fileName, totalCount);
                }
            }
        } catch (Exception e) {
            log.error("智能导出Excel异常：", e);
            throw new BusinessException("智能导出Excel异常：" + e.getMessage());
        }
    }
    
    /**
     * 处理小数据集的分块导出模式
     * 用于当数据量较小但客户端仍希望使用分块下载时
     */
    private ExportExcelResp handleSmallDatasetInChunkedMode(String businessType, Map<String, Object> params,
                                                          Map<String, String> headers, String fileName) {
        log.info("为小数据集启用分块导出模式");
        
        // 生成唯一的导出ID
        String exportId = UUID.randomUUID().toString();
        String tempFilePath = TEMP_DIR + File.separator + exportId + EXCEL_EXTENSION;
        File tempFile = new File(tempFilePath);
        
        try {
            // 创建临时文件
            try (FileOutputStream fileOut = new FileOutputStream(tempFile)) {
                // 执行导出
                easyExcelUtil.exportDataInternal(businessType, params, headers, () -> fileOut);
            }
            
            // 设置导出状态
            createAndStoreExportState(exportId, businessType, fileName, tempFilePath, tempFile.length());
            
            // 确保params包含默认的chunkIndex=0
            Map<String, Object> exportParams = new HashMap<>(params);
            exportParams.put(PARAM_CHUNK_INDEX, DEFAULT_CHUNK_INDEX);
            exportParams.put(PARAM_EXPORT_ID, exportId);
            
            // 执行第一个块的导出
            ExportExcelResp resp = exportExcelChunked(businessType, exportParams, headers, fileName);
            
            // 确保文件内容不为null，添加调试日志
            if (resp.getFileContent() == null) {
                log.error("严重错误: 文件内容为null");
                throw new BusinessException("导出文件内容为null");
            }
            
            log.info("小数据集分块导出成功, 文件大小: {}KB, 块大小: {}KB", 
                    tempFile.length() / 1024, resp.getFileContent().length / 1024);
                    
            return resp;
        } catch (Exception e) {
            log.error("处理小数据集分块导出异常：", e);
            throw new BusinessException("处理小数据集分块导出异常：" + e.getMessage());
        }
    }
    
    /**
     * 处理多文件拆分导出
     * 
     * @param businessType 业务类型
     * @param params 导出条件参数
     * @param headers 动态表头
     * @param fileName 文件名
     * @param totalCount 总记录数
     * @return 导出结果信息
     */
    private ExportExcelResp handleMultiFileExport(String businessType, Map<String, Object> params,
                                                Map<String, String> headers, String fileName,
                                                long totalCount) {
        // 计算需要拆分的文件数量
        int totalFiles = (int) Math.ceil((double) totalCount / MAX_RECORDS_PER_FILE);
        log.info("拆分为 {} 个文件导出，每个文件最多 {} 条记录", totalFiles, MAX_RECORDS_PER_FILE);
        
        // 生成唯一的导出ID
        String exportId = UUID.randomUUID().toString();
        
        // 保存多文件导出状态先，确保状态已创建
        saveMultiFileExportState(exportId, businessType, fileName, headers, 
                                 totalFiles, totalCount, params);
                                 
        // 保存拆分信息到导出参数中，用于前端后续请求
        Map<String, Object> exportParams = new HashMap<>(params);
        exportParams.put(PARAM_EXPORT_ID, exportId);
        exportParams.put("totalFiles", totalFiles);
        exportParams.put("totalRecords", totalCount);
        exportParams.put("maxRecordsPerFile", MAX_RECORDS_PER_FILE);
        exportParams.put(PARAM_CHUNK_INDEX, DEFAULT_CHUNK_INDEX); // 设置块索引
        
        // 处理第一个文件的导出（文件索引从1开始）
        Map<String, Object> firstFileParams = new HashMap<>(exportParams);
        firstFileParams.put(PARAM_FILE_INDEX, 1);
        firstFileParams.put(PARAM_START_RECORD, 0);
        firstFileParams.put(PARAM_END_RECORD, Math.min(MAX_RECORDS_PER_FILE, totalCount));
        
        try {
            // 执行第一个文件的分块导出
            ExportExcelResp resp = exportExcelChunked(businessType, firstFileParams, headers, 
                                                 getFileNameWithIndex(fileName, 1, totalFiles));
            
            // 设置多文件导出特有的信息
            resp.setIsMultiFileExport(true);
            resp.setTotalFiles(totalFiles);
            resp.setFileIndex(1);
            
            // 确保文件内容不为null
            if (resp.getFileContent() == null) {
                log.error("严重错误: 文件内容为null");
                throw new BusinessException("多文件导出的第一个文件内容为null");
            }
            
            log.info("多文件导出的第一个文件准备完成, 文件大小: {}KB", 
                    resp.getFileContent().length / 1024);
            
            return resp;
        } catch (Exception e) {
            // 如果导出失败，清理创建的状态
            cleanupExport(exportId);
            throw e;
        }
    }
    
    /**
     * 保存多文件导出状态
     */
    private void saveMultiFileExportState(String exportId, String businessType, String fileName,
                                        Map<String, String> headers, int totalFiles, 
                                        long totalRecords, Map<String, Object> params) {
        MultiFileExportState exportState = new MultiFileExportState();
        exportState.setExportId(exportId);
        exportState.setBusinessType(businessType);
        exportState.setFileName(fileName);
        exportState.setHeaders(headers != null ? new HashMap<>(headers) : null);
        exportState.setTotalFiles(totalFiles);
        exportState.setTotalRecords(totalRecords);
        exportState.setParams(params != null ? new HashMap<>(params) : new HashMap<>());
        
        // 存储导出状态
        multiFileExportStates.put(exportId, exportState);
        
        // 设置自动清理
        scheduleCleanup(exportId);
    }
    
    /**
     * 生成带索引的文件名
     * 例如：订单导出_1of5
     */
    private String getFileNameWithIndex(String fileName, int fileIndex, int totalFiles) {
        if (fileName == null || fileName.trim().isEmpty()) {
            fileName = "export";
        }
        
        // 移除扩展名（如果有）
        if (fileName.toLowerCase().endsWith(EXCEL_EXTENSION)) {
            fileName = fileName.substring(0, fileName.length() - EXCEL_EXTENSION.length());
        }
        
        return fileName + "_" + fileIndex + "of" + totalFiles;
    }
    
    /**
     * 存储多文件导出状态的静态Map
     * Key: 导出ID, Value: 导出状态
     */
    private static final Map<String, MultiFileExportState> multiFileExportStates = 
            Collections.synchronizedMap(new HashMap<>());
    
    /**
     * 多文件导出状态类
     */
    @Data
    private static class MultiFileExportState {
        // 导出ID
        private String exportId;
        // 业务类型
        private String businessType;
        // 文件名（不含索引和扩展名）
        private String fileName;
        // 动态表头
        private Map<String, String> headers;
        // 总文件数
        private int totalFiles;
        // 总记录数
        private long totalRecords;
        // 导出参数
        private Map<String, Object> params;
        // 已导出的文件索引列表
        private Set<Integer> exportedFileIndices = new HashSet<>();
    }

    /**
     * 处理多文件模式下的请求
     */
    private ExportExcelResp handleMultiFileRequest(MultiFileExportState multiFileState, 
                                                 Map<String, Object> params, 
                                                 Map<String, String> headers,
                                                 Integer chunkIndex) {
        // 获取文件索引
        int fileIndex = 1; // 默认为第一个文件
        if (params.containsKey(PARAM_FILE_INDEX)) {
            Object fileIndexObj = params.get(PARAM_FILE_INDEX);
            if (fileIndexObj != null) {
                fileIndex = Integer.parseInt(fileIndexObj.toString());
            }
        }
        
        // 确保文件索引在有效范围内
        if (fileIndex <= 0 || fileIndex > multiFileState.getTotalFiles()) {
            log.error("无效的文件索引: {}, 有效范围 1-{}", fileIndex, multiFileState.getTotalFiles());
            throw new BusinessException("无效的文件索引: " + fileIndex);
        }
        
        // 计算当前文件的数据范围
        long startRecord = (fileIndex - 1) * MAX_RECORDS_PER_FILE;
        long endRecord = Math.min(startRecord + MAX_RECORDS_PER_FILE, multiFileState.getTotalRecords());
        
        log.info("处理多文件导出请求: 文件索引={}, 范围={}-{}, 请求块索引={}", 
                fileIndex, startRecord, endRecord, chunkIndex);
        
        // 创建导出参数，增加数据范围限制
        Map<String, Object> exportParams = new HashMap<>(multiFileState.getParams());
        exportParams.put(PARAM_FILE_INDEX, fileIndex);
        exportParams.put(PARAM_START_RECORD, startRecord);
        exportParams.put(PARAM_END_RECORD, endRecord);
        
        // 增加分页参数 - 用于支持分页查询的导出提供者
        int pageSize = (int)Math.min(MAX_RECORDS_PER_FILE, endRecord - startRecord);
        exportParams.put("pageSize", pageSize);
        exportParams.put("pageNum", 1); // 只需要第一页，因为我们已经限制了startRecord和endRecord
        
        // 标记此文件已导出
        multiFileState.getExportedFileIndices().add(fileIndex);
        
        // 生成文件名
        String fileName = getFileNameWithIndex(multiFileState.getFileName(), fileIndex, multiFileState.getTotalFiles());
        
        // 获取导出提供者确认其存在
        ExportProvider<?> exportProvider = easyExcelUtil.getExportProvider(multiFileState.getBusinessType());
        if (exportProvider == null) {
            throw new BusinessException("未找到业务类型 [" + multiFileState.getBusinessType() + "] 对应的导出提供者");
        }
        
        // 记录导出范围
        log.info("导出文件 {}/{}, 数据范围 {}-{}, 总数据量 {}", 
                fileIndex, multiFileState.getTotalFiles(), startRecord, endRecord, 
                multiFileState.getTotalRecords());
        
        // 使用传入的自定义表头（如果有）或者默认表头
        Map<String, String> headersToUse = headers != null && !headers.isEmpty() 
            ? headers  // 使用客户自定义的动态表头
            : multiFileState.getHeaders();  // 使用保存的默认表头
        
        // 如果使用了自定义表头，记录日志
        if (headers != null && !headers.isEmpty() && !headers.equals(multiFileState.getHeaders())) {
            log.info("使用客户自定义动态表头，表头数量: {}", headers.size());
        }
        
        // 如果是第一个块，则需要创建新文件
        if (chunkIndex == 0) {
            // 确保params包含chunkIndex
            exportParams.put(PARAM_CHUNK_INDEX, chunkIndex);
            return handleFirstChunkRequest(multiFileState.getBusinessType(), 
                                          exportParams, 
                                          headersToUse, 
                                          fileName);
        } else {
            // 确保params包含chunkIndex
            exportParams.put(PARAM_CHUNK_INDEX, chunkIndex);
            return handleSubsequentChunkRequest(extractExportId(exportParams), chunkIndex);
        }
    }

    /**
     * 处理多文件打包导出
     * 将多个Excel文件打包为一个ZIP文件下载
     * 
     * @param businessType 业务类型
     * @param params 导出条件参数
     * @param headers 动态表头
     * @param fileName 文件名
     * @param totalCount 总记录数
     * @return 导出结果信息
     */
    private ExportExcelResp handleMultiFilePackagedExport(String businessType, Map<String, Object> params,
                                                       Map<String, String> headers, String fileName,
                                                       long totalCount) {
        // 计算需要拆分的文件数量
        int totalFiles = (int) Math.ceil((double) totalCount / MAX_RECORDS_PER_FILE);
        log.info("打包模式：拆分为 {} 个文件导出并打包，每个文件最多 {} 条记录", totalFiles, MAX_RECORDS_PER_FILE);
        
        // 生成唯一的导出ID
        String exportId = UUID.randomUUID().toString();
        String zipFilePath = TEMP_DIR + File.separator + exportId + ".zip";
        
        try {
            // 创建ZIP文件
            try (FileOutputStream fos = new FileOutputStream(zipFilePath);
                 ZipOutputStream zipOut = new ZipOutputStream(fos)) {
                
                // 遍历生成所有文件
                for (int fileIndex = 1; fileIndex <= totalFiles; fileIndex++) {
                    // 计算当前文件的记录范围
                    long startRecord = (fileIndex - 1) * MAX_RECORDS_PER_FILE;
                    long endRecord = Math.min(fileIndex * MAX_RECORDS_PER_FILE, totalCount);
                    
                    // 创建当前文件参数
                    Map<String, Object> fileParams = new HashMap<>(params);
                    fileParams.put(PARAM_START_RECORD, startRecord);
                    fileParams.put(PARAM_END_RECORD, endRecord);
                    fileParams.put(PARAM_FILE_INDEX, fileIndex);
                    
                    // 生成当前文件名（确保文件名包含.xlsx扩展名）
                    String currentFileName = getFileNameWithIndex(fileName, fileIndex, totalFiles);
                    // 添加.xlsx扩展名（如果没有）
                    if (!currentFileName.toLowerCase().endsWith(EXCEL_EXTENSION)) {
                        currentFileName += EXCEL_EXTENSION;
                    }
                    
                    // 添加当前文件到ZIP包
                    exportAndAddFileToZip(businessType, fileParams, headers, currentFileName, zipOut, startRecord, endRecord);
                }
            }
            
            // 创建导出状态
            File zipFile = new File(zipFilePath);
            
            // 确保ZIP文件名不带.xlsx后缀
            String zipFileName = fileName;
            if (zipFileName.toLowerCase().endsWith(EXCEL_EXTENSION)) {
                zipFileName = zipFileName.substring(0, zipFileName.length() - EXCEL_EXTENSION.length());
            }
            zipFileName += ".zip";
            
            // 创建响应对象
            ExportExcelResp resp = new ExportExcelResp();
            resp.setExportId(exportId);
            resp.setChunkId(exportId); // 保持兼容性
            resp.setFileName(zipFileName);
            resp.setFileType("zip");
            resp.setFileSize(zipFile.length());
            resp.setTotalChunks(1);
            resp.setChunkIndex(0);
            resp.setIsLastChunk(true);
            resp.setIsPackagedZip(true);
            
            // 读取ZIP文件内容
            byte[] fileContent = Files.readAllBytes(zipFile.toPath());
            resp.setFileContent(fileContent);
            
            // 设置清理任务
            scheduleCleanup(exportId);
            
            log.info("多文件打包导出完成, ZIP大小: {}KB", zipFile.length() / 1024);
            
            return resp;
        } catch (Exception e) {
            log.error("处理多文件打包导出异常：", e);
            throw new BusinessException("处理多文件打包导出异常：" + e.getMessage());
        }
    }
    
    /**
     * 导出数据到临时文件并添加到ZIP
     * 
     * @param businessType 业务类型
     * @param fileParams 导出参数
     * @param headers 表头
     * @param fileName 文件名
     * @param zipOut ZIP输出流
     * @param startRecord 起始记录
     * @param endRecord 结束记录
     * @throws IOException 如果IO操作异常
     */
    private void exportAndAddFileToZip(String businessType, Map<String, Object> fileParams, 
                                      Map<String, String> headers, String fileName, 
                                      ZipOutputStream zipOut,
                                      long startRecord, long endRecord) throws IOException {
        // 创建临时文件
        File tempFile = File.createTempFile("export_", EXCEL_EXTENSION);
        try {
            // 导出数据到临时文件
            try (FileOutputStream fileOut = new FileOutputStream(tempFile)) {
                easyExcelUtil.exportDataInternal(businessType, fileParams, headers, () -> fileOut);
            }
            
            // 添加到ZIP
            addFileToZip(zipOut, tempFile, fileName);
            
            log.info("已添加文件到ZIP包: {}，记录范围: {}-{}", fileName, startRecord, endRecord);
        } finally {
            // 删除临时文件
            if (tempFile.exists()) {
                try {
                    Files.delete(tempFile.toPath());
                    log.debug("已删除临时文件: {}", tempFile.getAbsolutePath());
                } catch (IOException ex) {
                    log.warn("删除临时文件失败: {}", tempFile.getAbsolutePath(), ex);
                    // 标记为JVM退出时删除
                    tempFile.deleteOnExit();
                }
            }
        }
    }
    
    /**
     * 将文件添加到ZIP输出流中
     */
    private void addFileToZip(ZipOutputStream zipOut, File file, String entryName) throws IOException {
        try (FileInputStream fis = new FileInputStream(file)) {
            ZipEntry zipEntry = new ZipEntry(entryName);
            zipOut.putNextEntry(zipEntry);
            
            byte[] buffer = new byte[1024];
            int length;
            while ((length = fis.read(buffer)) >= 0) {
                zipOut.write(buffer, 0, length);
            }
            
            zipOut.closeEntry();
        }
    }

    @Override
    public void exportWord(ExportWordReq request, HttpServletResponse response) {
        log.info("开始导出Word文档，业务类型: {}, 模板: {}", request.getBusinessType(), request.getTemplateName());
        
        if (request.getBusinessType() != null && !request.getBusinessType().trim().isEmpty() && 
                documentProviderFactory.hasProvider(request.getBusinessType())) {
            
            // 使用业务处理器生成数据
            DocumentProvider provider = documentProviderFactory.getProvider(request.getBusinessType());
            Map<String, Object> placeholderData = provider.prepareData(request.getPlaceholderData());
            
            // 处理模板名称
            String templateName = request.getTemplateName();
            if (templateName == null || templateName.trim().isEmpty()) {
                templateName = provider.getTemplateName(request.getPlaceholderData());
            }
            
            // 设置其他可选参数
            if (request.getHeader() == null) {
                request.setHeader(provider.getHeader(request.getPlaceholderData()));
            }
            
            if (request.getFooter() == null) {
                request.setFooter(provider.getFooter(request.getPlaceholderData()));
            }
            
            if (request.getWatermarkText() == null) {
                request.setWatermarkText(provider.getWatermarkText(request.getPlaceholderData()));
            }
            
            if (request.getWatermarkColor() == null) {
                request.setWatermarkColor(provider.getWatermarkColor(request.getPlaceholderData()));
            }
            
            if (request.getShowPageNumbers() == null) {
                request.setShowPageNumbers(provider.showPageNumbers(request.getPlaceholderData()));
            }
            
            // 使用动态生成的参数替换占位符
            request.setPlaceholderData(placeholderData);
            request.setTemplateName(templateName);
        }
        
        // 验证必要参数
        if (request.getTemplateName() == null || request.getTemplateName().trim().isEmpty()) {
            throw new IllegalArgumentException("模板名称不能为空");
        }
        
        File templateFile = getWordTemplate(request.getTemplateName());
        if (templateFile == null || !templateFile.exists()) {
            throw new IllegalArgumentException("找不到指定模板：" + request.getTemplateName());
        }

        try (FileInputStream fis = new FileInputStream(templateFile);
             XWPFDocument document = new XWPFDocument(fis)) {
            
            // 1. 填充数据
            if (request.getPlaceholderData() != null && !request.getPlaceholderData().isEmpty()) {
                DocumentUtil.replaceTextPlaceholders(document, request.getPlaceholderData());
            }
            
            // 2. 添加页眉
            if (StringUtils.hasText(request.getHeader())) {
                DocumentUtil.addHeader(document, request.getHeader());
            }
            
            // 3. 添加页脚和页码
            boolean showPageNumbers = request.getShowPageNumbers() != null && request.getShowPageNumbers();
            DocumentUtil.addFooter(document, request.getFooter(), showPageNumbers);
            
            // 4. 添加水印
            if (StringUtils.hasText(request.getWatermarkText())) {
                DocumentUtil.addWatermark(document, request.getWatermarkText(), request.getWatermarkColor());
            }
            
            // 5. 生成Word文档
            String fileName = StringUtils.hasText(request.getFileName()) 
                    ? request.getFileName() 
                    : "document_" + UUID.randomUUID().toString().substring(0, 8);
            
            if (!fileName.toLowerCase().endsWith(".docx")) {
                fileName += ".docx";
            }
            
            // 设置响应头
            response.setContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document");
            response.setHeader("Content-Disposition", "attachment; filename=" + 
                    URLEncoder.encode(fileName, StandardCharsets.UTF_8.name()));
            
            // 写入响应
            document.write(response.getOutputStream());
            log.info("Word文档导出成功: {}", fileName);
            
        } catch (Exception e) {
            log.error("导出Word文档失败", e);
            throw new RuntimeException("导出Word文档失败: " + e.getMessage(), e);
        }
    }

    @Override
    public boolean convertWordToPdf(InputStream wordInputStream, OutputStream pdfOutputStream) {
        try {
            // 使用POI+iTextPDF手动方式或documents4j
            return DocumentUtil.convertWordToPdf(wordInputStream, pdfOutputStream);
        } catch (Exception e) {
            log.error("Word转PDF失败", e);
            return false;
        }
    }

    @Override
    public void exportWordAsPdf(ExportWordReq request, HttpServletResponse response) {
        log.info("开始导出PDF文档，业务类型: {}, 模板: {}", request.getBusinessType(), request.getTemplateName());
        
        if (request.getBusinessType() != null && !request.getBusinessType().trim().isEmpty() && 
                documentProviderFactory.hasProvider(request.getBusinessType())) {
                
            // 使用业务处理器生成数据
            DocumentProvider provider = documentProviderFactory.getProvider(request.getBusinessType());
            Map<String, Object> placeholderData = provider.prepareData(request.getPlaceholderData());
            
            // 处理模板名称
            String templateName = request.getTemplateName();
            if (templateName == null || templateName.trim().isEmpty()) {
                templateName = provider.getTemplateName(request.getPlaceholderData());
            }
            
            // 设置其他可选参数
            if (request.getHeader() == null) {
                request.setHeader(provider.getHeader(request.getPlaceholderData()));
            }
            
            if (request.getFooter() == null) {
                request.setFooter(provider.getFooter(request.getPlaceholderData()));
            }
            
            if (request.getWatermarkText() == null) {
                request.setWatermarkText(provider.getWatermarkText(request.getPlaceholderData()));
            }
            
            if (request.getWatermarkColor() == null) {
                request.setWatermarkColor(provider.getWatermarkColor(request.getPlaceholderData()));
            }
            
            if (request.getShowPageNumbers() == null) {
                request.setShowPageNumbers(provider.showPageNumbers(request.getPlaceholderData()));
            }
            
            // 使用动态生成的参数替换占位符
            request.setPlaceholderData(placeholderData);
            request.setTemplateName(templateName);
        }
        
        if (request.getTemplateName() == null || request.getTemplateName().trim().isEmpty()) {
            throw new IllegalArgumentException("模板名称不能为空");
        }
        
        // 第一步：创建临时文件存放Word内容
        File tempWordFile = null;
        File tempPdfFile = null;
        
        try {
            // 确保临时目录存在
            Path tempDir = Paths.get(tempPath);
            if (!Files.exists(tempDir)) {
                Files.createDirectories(tempDir);
            }
            
            String tempFileName = UUID.randomUUID().toString();
            tempWordFile = new File(tempDir.toFile(), tempFileName + ".docx");
            tempPdfFile = new File(tempDir.toFile(), tempFileName + ".pdf");
            
            // 创建临时响应流，用于生成Word文档
            try (FileOutputStream wordOut = new FileOutputStream(tempWordFile)) {
                // 获取模板
                File templateFile = getWordTemplate(request.getTemplateName());
                if (templateFile == null || !templateFile.exists()) {
                    throw new IllegalArgumentException("找不到指定模板：" + request.getTemplateName());
                }
                
                try (FileInputStream fis = new FileInputStream(templateFile);
                     XWPFDocument document = new XWPFDocument(fis)) {
                    
                    // 1. 填充数据
                    if (request.getPlaceholderData() != null && !request.getPlaceholderData().isEmpty()) {
                        DocumentUtil.replaceTextPlaceholders(document, request.getPlaceholderData());
                    }
                    
                    // 2. 添加页眉
                    if (StringUtils.hasText(request.getHeader())) {
                        DocumentUtil.addHeader(document, request.getHeader());
                    }
                    
                    // 3. 添加页脚和页码
                    boolean showPageNumbers = request.getShowPageNumbers() != null && request.getShowPageNumbers();
                    DocumentUtil.addFooter(document, request.getFooter(), showPageNumbers);
                    
                    // 4. 添加水印
                    if (StringUtils.hasText(request.getWatermarkText())) {
                        DocumentUtil.addWatermark(document, request.getWatermarkText(), request.getWatermarkColor());
                    }
                    
                    // 5. 将Word文档写入临时文件
                    document.write(wordOut);
                }
            }
            
            // 将Word转为PDF
            try (FileInputStream wordIn = new FileInputStream(tempWordFile);
                 FileOutputStream pdfOut = new FileOutputStream(tempPdfFile)) {
                
                boolean success = convertWordToPdf(wordIn, pdfOut);
                if (!success) {
                    throw new RuntimeException("Word转PDF失败");
                }
            }
            
            // 设置响应头
            String fileName = StringUtils.hasText(request.getFileName()) 
                    ? request.getFileName() 
                    : "document_" + UUID.randomUUID().toString().substring(0, 8);
                    
            if (!fileName.toLowerCase().endsWith(".pdf")) {
                fileName += ".pdf";
            }
            
            response.setContentType("application/pdf");
            response.setHeader("Content-Disposition", "attachment; filename=" + 
                    URLEncoder.encode(fileName, StandardCharsets.UTF_8.name()));
            
            // 将PDF文件写入响应
            try (FileInputStream pdfIn = new FileInputStream(tempPdfFile);
                 OutputStream out = response.getOutputStream()) {
                byte[] buffer = new byte[4096];
                int bytesRead;
                while ((bytesRead = pdfIn.read(buffer)) != -1) {
                    out.write(buffer, 0, bytesRead);
                }
            }
            
            log.info("PDF文档导出成功: {}", fileName);
            
        } catch (Exception e) {
            log.error("导出PDF文档失败", e);
            throw new RuntimeException("导出PDF文档失败: " + e.getMessage(), e);
        } finally {
            // 清理临时文件
            cleanupTempFiles(tempWordFile, tempPdfFile);
        }
    }

    @Override
    public File getWordTemplate(String templateName) {
        if (templateName == null || templateName.trim().isEmpty()) {
            return null;
        }
        
        // 确保模板名称没有路径分隔符，防止路径遍历攻击
        templateName = templateName.replace("../", "").replace("..\\", "");
        
        // 如果模板名称没有.docx后缀，添加后缀
        if (!templateName.toLowerCase().endsWith(".docx")) {
            templateName += ".docx";
        }
        
        try {
            // 处理classpath路径
            if (templatePath.startsWith("classpath:")) {
                String classpathLocation = templatePath.substring("classpath:".length());
                // 规范化路径
                if (!classpathLocation.endsWith("/")) {
                    classpathLocation += "/";
                }
                
                String resourcePath = classpathLocation + templateName;
                // 尝试从classpath获取模板
                org.springframework.core.io.Resource resource = 
                        new org.springframework.core.io.ClassPathResource(resourcePath);
                
                if (resource.exists()) {
                    // 创建临时文件
                    File tempFile = File.createTempFile("template_", ".docx");
                    try (InputStream is = resource.getInputStream();
                         FileOutputStream os = new FileOutputStream(tempFile)) {
                        byte[] buffer = new byte[1024];
                        int length;
                        while ((length = is.read(buffer)) > 0) {
                            os.write(buffer, 0, length);
                        }
                    }
                    
                    // 设置退出时删除临时文件
                    tempFile.deleteOnExit();
                    return tempFile;
                } else {
                    log.warn("在classpath中找不到模板文件: {}", resourcePath);
                }
            } else {
                // 处理文件系统路径
                Path templateDir = Paths.get(templatePath);
                Path templateFile = templateDir.resolve(templateName);
                
                File file = templateFile.toFile();
                if (file.exists() && file.isFile()) {
                    return file;
                }
            }
        } catch (Exception e) {
            log.error("获取Word模板时发生错误", e);
        }
        
        return null;
    }
    
    /**
     * 清理临时文件
     */
    private void cleanupTempFiles(File... files) {
        if (files == null) {
            return;
        }
        
        for (File file : files) {
            if (file != null && file.exists()) {
                boolean deleted = file.delete();
                if (!deleted) {
                    log.warn("清理临时文件失败: {}", file.getAbsolutePath());
                    // 添加到待清理文件列表，后续定时任务清理
                    file.deleteOnExit();
                }
            }
        }
    }

    /**
     * 解析业务类型字符串
     * 支持以下格式:
     * 1. 带引号的逗号分隔格式: "CUST_CREDIT","INVOICE"
     * 2. 不带引号的逗号分隔格式: CUST_CREDIT,INVOICE
     * 3. JSON数组格式: ["CUST_CREDIT","INVOICE"]
     * 4. JSON对象格式: {"CUST_CREDIT","INVOICE"}
     * 
     * @param businessTypes 业务类型字符串
     * @return 业务类型列表
     */
    private List<String> parseBusinessTypes(String businessTypes) {
        // 去除前后空格
        String trimmed = businessTypes.trim();
        
        // 判断是否是JSON格式
        if (isJsonFormat(trimmed)) {
            String content = extractJsonContent(trimmed);
            return parseCommaSeparatedFormat(content);
        } else {
            // 普通逗号分隔格式
            return parseCommaSeparatedFormat(trimmed);
        }
    }
    
    /**
     * 判断字符串是否为JSON格式
     * 数组格式: ["CUST_CREDIT","INVOICE"]
     * 对象格式: {"CUST_CREDIT","INVOICE"}
     */
    private boolean isJsonFormat(String str) {
        return (str.startsWith("[") && str.endsWith("]")) 
            || (str.startsWith("{") && str.endsWith("}"));
    }
    
    /**
     * 提取JSON内容
     * 去除最外层的括号[]或{}
     */
    private String extractJsonContent(String jsonStr) {
        return jsonStr.substring(1, jsonStr.length() - 1);
    }
    
    /**
     * 解析逗号分隔的格式
     */
    private List<String> parseCommaSeparatedFormat(String commaSeparated) {
        List<String> result = new ArrayList<>();
        
        // 定义模式：匹配带引号的内容或不带引号的内容
        Pattern pattern = Pattern.compile("\"([^\"]*)\"|([^,]+)");
        Matcher matcher = pattern.matcher(commaSeparated);
        
        while (matcher.find()) {
            String matched = matcher.group(1) != null ? matcher.group(1) : matcher.group(2);
            if (matched != null && !matched.trim().isEmpty()) {
                result.add(matched.trim());
            }
        }
        
        return parseCommaSeparatedContent(String.join(",", result));
    }
    
    /**
     * 进一步处理逗号分隔的内容
     */
    private List<String> parseCommaSeparatedContent(String content) {
        List<String> result = new ArrayList<>();
        String[] parts = content.split(",");
        
        for (String part : parts) {
            part = part.trim();
            if (!part.isEmpty()) {
                // 处理类型映射，例如 CUST_CREDIT:10
                String type = part;
                if (part.contains(":")) {
                    type = part.substring(0, part.indexOf(":")).trim();
                }
                result.add(type);
            }
        }
        
        return result;
    }
}

